// Package format 提供格式化文件功能
//
//	如果被格式化的文件不符合语法，进行格式化会发生的行为未定义
package format

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"
	"sync"

	"gitee.com/u-language/u-language/ucom/errcode"
	"gitee.com/u-language/u-language/ucom/internal/utils"
	"gitee.com/u-language/u-language/ucom/lex"
)

// format 用于格式化一些词法分析结果
type format struct {
	err  error
	buf  strings.Builder
	Tabs int
	//左右大括号未匹配数量
	matchbrace int
	//case或default代码块的制表符
	caseOrDefaultTab int
	incaseOrDefault  bool
	inImport         bool //是否在import里
}

func newformat() format {
	return format{}
}

// FormatFile格式化一些词法分析结果
func (f *format) FormatFile(line []lex.Line) {
	defer func() {
		if err := recover(); err != nil {
			if s, ok := err.(string); ok && s == "strings: negative Repeat count" { //如果发生调用 [strings.Repeat]传参负数
				f.err = KeyNoMatchRightBrace
			} else { //如果是未知错误
				panic(err)
			}
		}
	}()
	for _, v := range line {
		f.WriteLine(v)
		if f.err != nil {
			return
		}
	}
}

// WriteLine 写入一行单词
func (f *format) WriteLine(line lex.Line) {
	var once sync.Once
	ok := false
write:
	for i, tk := range line.Value {
		switch tk.TYPE {
		case lex.Switch, lex.Import:
			switch tk.TYPE {
			case lex.Switch:
				f.matchbrace = 0
			case lex.Import:
				f.inImport = true
			}
			fallthrough
		case lex.FUNC, lex.Method, lex.IF, lex.ELSE, lex.FOR, lex.AutoFree, lex.Struct, lex.Enum: //这些和上面的是一定加制表符的代码块
			f.buf.WriteString(strings.Repeat("\t", f.Tabs))
			ok = true
			f.matchbrace++
			f.Tabs++
		case lex.RBRACE: //假设是符合语法的程序文本，右大括号说明一个代码块的结束，减少一个制表符
			f.Tabs--
			if f.incaseOrDefault { //如果在case或dafault里
				f.matchbrace--
				if f.matchbrace == 0 { //假设是符合语法的程序文本，如果括号数量匹配说明到switch的某尾，case或default代码块结束了
					f.incaseOrDefault, f.caseOrDefaultTab = false, 0
				}
			}
			if f.inImport { //如果在import里
				f.inImport = false
			}
			ok = true
			f.buf.WriteString(strings.Repeat("\t", f.Tabs))
		case lex.Case, lex.Default: //假设是符合语法的程序文本，case或dafelut代码块只用比switch代码块多一个制表符
			ok = true
			if !f.incaseOrDefault { //记录在case或default代码块里
				f.buf.WriteString(strings.Repeat("\t", f.Tabs))
				f.incaseOrDefault, f.caseOrDefaultTab = true, f.Tabs
				f.Tabs++
			} else { //case或default代码块的制表符初始值为switch的制表符加1
				f.buf.WriteString(strings.Repeat("\t", f.caseOrDefaultTab))
				f.Tabs = f.caseOrDefaultTab + 1
			}
		case lex.RPAREN:
			fallthrough
		case lex.LPAREN: //左小括号直接原原本本的输出
			f.buf.WriteString(tk.Value)
			continue write
		default:
			once.Do(func() { //确保在每一行开头都加制表符
				if !ok {
					f.buf.WriteString(strings.Repeat("\t", f.Tabs))
				}
			})
		}
		writeToken(&f.buf, tk)
		if i+1 <= len(line.Value)-1 && no_leave_a_blank_space(tk.TYPE, line.Value[i+1].TYPE) { //如果后面不是左小括号或右小括号或自操作运算符
			f.buf.WriteString(" ")
		}
	}
	f.buf.WriteString("\n")
}

func writeToken(buf *strings.Builder, tk lex.Token) {
	if tk.TYPE == lex.MoreThanJustTokensWithBrackts {
		tks := *tk.PtrMoreThanJustTokensWithBracktsS().Ptr
		for _, v := range tks {
			writeToken(buf, v)
		}
		return
	}
	buf.WriteString(tk.Value)
}

func no_leave_a_blank_space(local, typ lex.TokenType) bool {
	if local == lex.TokenWithBrackets && typ == lex.MoreThanJustTokensWithBrackts {
		return false
	}
	return typ != lex.LPAREN && typ != lex.RPAREN && typ != lex.Dec && typ != lex.Inc
}

// Error after Lexical analysis
var ErrorAfterLex = errors.New("词法分析后有错误")

// Keyword does not match the number of right curly braces
var KeyNoMatchRightBrace = errors.New("关键字与右大括号数量不匹配")

// FormatFile 读取一个文件并格式化
// 假设词法分析前errctx没有保存过报错
func FormatFile(path string, errctx *errcode.ErrCtx) error {
	fd, err := os.OpenFile(path, os.O_RDWR, 0777)
	if err != nil {
		return err
	}
	value, err := utils.ReadAllLine(bufio.NewReader(fd))
	if err != nil {
		return err
	}
	ft := lex.Lex(path, value, errctx, true)
	if errctx.Errbol() {
		return ErrorAfterLex
	}
	format := newformat()
	format.FormatFile(ft.Value)
	if format.err != nil {
		return fmt.Errorf("格式化文件时出错，错误=%w", format.err)
	}
	err = fd.Truncate(0)
	if err != nil {
		return fmt.Errorf("写入格式化代码前清空文件失败，错误=%w", err)
	}
	_, err = fd.Seek(0, io.SeekStart)
	if err != nil {
		return fmt.Errorf("写入格式化代码前调用Seek失败，错误=%w", err)
	}
	_, err = fd.WriteString(format.buf.String())
	if err == nil {
		return nil
	}
	return fmt.Errorf("写入格式化代码失败，错误=%w", err)
}
